﻿using System;
using System.Linq;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;

namespace MsdnExample
{
    #region PrimeNumberCalculator Implementation

    // This class implements the Event-based Asynchronous Pattern.
    // It asynchronously computes whether a number is prime or
    // composite (not prime).
    public class AsyncFuncPrimeNumberCalculator
    {
        private AsyncFuncMultipleInvocation<int, CalculatePrimeResult> _func;

        /////////////////////////////////////////////////////////////
        #region Public events

        public event System.ComponentModel.ProgressChangedEventHandler ProgressChanged
        {
            add { _func.ProgressChanged += value; }
            remove { _func.ProgressChanged -= value; }
        }
        public event AsyncFuncCompletedEventHandler<CalculatePrimeResult> CalculatePrimeCompleted
        {
            add { _func.Completed += value; }
            remove { _func.Completed -= value; }
        }

        #endregion

        /////////////////////////////////////////////////////////////
        #region Construction and destruction

        public AsyncFuncPrimeNumberCalculator()
        {
            _func = new AsyncFuncMultipleInvocation<int, CalculatePrimeResult>(CalculateWorker);
            _func.Sender = this;
        }

        #endregion // Construction and destruction

        /////////////////////////////////////////////////////////////
        ///
        #region Implementation

        // This method starts an asynchronous calculation. 
        // First, it checks the supplied task ID for uniqueness.
        // If taskId is unique, it creates a new WorkerEventHandler 
        // and calls its BeginInvoke method to start the calculation.
        public virtual void CalculatePrimeAsync(
            int numberToTest,
            object taskId)
        {
            _func.InvokeAsync(numberToTest,
                taskId);
        }

        // This method cancels a pending asynchronous operation.
        public void CancelAsync(object taskId)
        {
            _func.Cancel(taskId);
        }

        // This method performs the actual prime number computation.
        // It is executed on the worker thread.
        private CalculatePrimeResult CalculateWorker(
            int numberToTest,
            Func<bool> cancelled,
            Action<int> reportProgress)
        {
            bool isPrime = false;
            int firstDivisor = 1;

            // Check that the task is still active.
            // The operation may have been canceled before
            // the thread was scheduled.
            if (!cancelled())
            {
                // Find all the prime numbers up to 
                // the square root of numberToTest.
                ArrayList primes = BuildPrimeNumberList(
                    numberToTest,
                    cancelled,
                    reportProgress);

                // Now we have a list of primes less than
                // numberToTest.
                isPrime = IsPrime(
                    primes,
                    numberToTest,
                    out firstDivisor);
            }

            return new CalculatePrimeResult(
                numberToTest,
                firstDivisor,
                isPrime);
        }

        // This method computes the list of prime numbers used by the
        // IsPrime method.
        private ArrayList BuildPrimeNumberList(
            int numberToTest,
            Func<bool> cancelled,
            Action<int> reportProgress)
        {
            ArrayList primes = new ArrayList();
            int firstDivisor;
            int n = 5;

            // Add the first prime numbers.
            primes.Add(2);
            primes.Add(3);

            // Do the work.
            while (n < numberToTest &&
                   !cancelled())
            {
                if (IsPrime(primes, n, out firstDivisor))
                {
                    // Report to the client that a prime was found.
                    reportProgress(
                        (int)((float)n / (float)numberToTest * 100));
                        
                    primes.Add(n);

                    // Yield the rest of this time slice.
                    Thread.Sleep(0);
                }

                // Skip even numbers.
                n += 2;
            }

            return primes;
        }

        // This method tests n for primality against the list of 
        // prime numbers contained in the primes parameter.
        private bool IsPrime(
            ArrayList primes,
            int n,
            out int firstDivisor)
        {
            bool foundDivisor = false;
            bool exceedsSquareRoot = false;

            int i = 0;
            int divisor = 0;
            firstDivisor = 1;

            // Stop the search if:
            // there are no more primes in the list,
            // there is a divisor of n in the list, or
            // there is a prime that is larger than 
            // the square root of n.
            while (
                (i < primes.Count) &&
                !foundDivisor &&
                !exceedsSquareRoot)
            {
                // The divisor variable will be the smallest 
                // prime number not yet tried.
                divisor = (int)primes[i++];

                // Determine whether the divisor is greater
                // than the square root of n.
                if (divisor * divisor > n)
                {
                    exceedsSquareRoot = true;
                }
                // Determine whether the divisor is a factor of n.
                else if (n % divisor == 0)
                {
                    firstDivisor = divisor;
                    foundDivisor = true;
                }
            }

            return !foundDivisor;
        }

        #endregion

    }

    public class CalculatePrimeResult
    {
        public CalculatePrimeResult(
            int numberToTestValue, 
            int firstDivisorValue, 
            bool isPrimeValue)
        {
            NumberToTest = numberToTestValue;
            FirstDivisor = firstDivisorValue;
            IsPrime = IsPrime;
        }

        public int NumberToTest { get; private set; }
        public int FirstDivisor { get; private set; }
        public bool IsPrime { get; private set; }
    }


    #endregion
}
